韓琛準備派遣多個身家較為清白的小弟臥底至香港警隊,包括建明。他向小弟們講述著自己的過去,並說自己不相信算命先生所說的「一將功成萬骨枯」。他認為出來混的,未來的路怎麼走應該由自己決定。
在開始進行所有query前,我們需要先告知EdgeDB初始的schema。
edgedb migration create
後,再輸入edgedb migrate
。EdgeDB REPL
,可以使用更便捷的\migration create
及\migrate
指令。insert
此場景時間1992年insert FuzzyTime {fuzzy_year:= 1992};
insert
韓琛及其演員曾志偉韓琛於開頭就說出「一將功成萬骨枯」的經典句,我們將此句收錄在classic_lines property
中。
此外,雖然actors
為multi link
,可以包括多個演員。但是我們可以使用assert_single()
來確保最多只會接收到一個曾志偉Actor object
。這麼一來,如果資料庫內已經有兩個Actor object
的name
都叫曾志偉時,這個query就會報錯。
insert Actor {
name:= "曾志偉",
eng_name:= "Eric",
nickname:= "獎老",
};
insert GangsterBoss {
name:= "韓琛",
nickname:= "琛哥",
classic_lines:= ["一將功成萬骨枯"],
actors := assert_single((select Actor filter .name = "曾志偉")),
};
另一種作法是觀察想選擇的object
是否有constraint exclusive
的property
可以作為filter
。如果有的話,即代表我們最多只會選擇到一個object
,此時就不需要額外使用assert_single()
了。這裡由於Actor object
沒有constraint exclusive
的property
,所以無法使用這個作法。
insert
劉建明及其少年時期演員陳冠希insert Actor {
name:= "陳冠希",
eng_name:= "Edison",
};
insert GangsterSpy {
name:= "劉建明",
nickname:= "劉仔",
gangster_boss:= assert_single((select GangsterBoss filter .name = "韓琛")),
dept:= "警校學生",
actors := assert_single((select Actor filter .name in {"陳冠希"})),
};
語法與前面類似,留意filter
時也可以試試用in {}
的寫法。
alias
由於每次都要使用(select ... filter ... .xxx=ooo)
的語法來選擇object
頗為麻煩,針對常用到的object
,可以直接在schema中建立alias
,方便取用。我們這邊定義了一個hon
(韓琛)、lau
(劉建明)及year_1992
(1992年)的alias
:
alias hon:= assert_exists(
assert_single(
(select GangsterBoss filter .name = "韓琛")
)
);
alias lau:= assert_exists(
assert_single(
(select GangsterSpy filter .name = "劉建明")
)
);
alias year_1992:= assert_exists(assert_single((select FuzzyTime
filter .fuzzy_year = 1992
and .fuzzy_month ?= <FuzzyMonth>{}
and .fuzzy_day ?= <FuzzyDay>{}
and .fuzzy_hour ?= <FuzzyHour>{}
and .fuzzy_minute ?= <FuzzyMinute>{}
and .fuzzy_second ?= <FuzzySecond>{}
and .fuzzy_dow ?= <DayOfWeek>{}
))
);
於year_1992
中,我們用到了?=operator
。?=
除了像=
可以比較兩個set
外,還可以比較空set
。當兩個set
都為空時,會返回true
。當有些property
可以為空set
時,?=
是個非常好用的工具。另外,別忘了空EdgeDBset
需要定義型別。
alias year_1992
的另一種寫法也可以使用fuzzy_fmt
這個computed property
來作為filter
的條件:
alias year_1992:= assert_exists(
assert_single(
(
select FuzzyTime
filter .fuzzy_fmt="1992/MM/DD_HH24:MI:SS_ID"
)
)
);
這種寫法比較快速,是我實際寫query會用的方法。但在定義schema時,我反而比較喜歡原先那種直接了當的寫法。
alias
的function
您可能有留意到,我們在alias
前都加上了assert_exists()
及assert_single()
,這樣可以確保每個alias
只會返回剛好一個object
。我自己會習慣寫一個名為test_alias()
的function
來做測試:
function test_alias() -> bool
using (all({
test_scene01_alias(),
})
);
function test_scene01_alias() -> bool
using (all({
(exists hon),
(exists lau),
(exists year_1992),
})
);
另外提醒習慣寫Python的朋友,定義function
時,在()
後不需要加上:
。
test_alias()
中會包含多個場景的sub-test(如test_scene01_alias()
),當每一個場景的sub-test
都返回true
時,all
會返回true
,否則會報錯。而我們利用exists
檢查各場景中的alias
是否存在,如果全部都存在的話,all
會返回true
,否則會報錯:
edgedb error: CardinalityViolationError: assert_exists violation: expression returned an empty set
這麼一來當我們在操作資料庫時,可以隨時透過test_alias()
來確認每一個alias
,是否都如預期地返回了剛好一個object
。
did you create alias 'default::hon'? [y,n,l,c,b,s,q,?]
> y
did you create alias 'default::lau'? [y,n,l,c,b,s,q,?]
> y
did you create alias 'default::year_1992'? [y,n,l,c,b,s,q,?]
> y
did you create function 'default::test_scene01_alias'? [y,n,l,c,b,s,q,?]
> y
did you create function 'default::test_alias'? [y,n,l,c,b,s,q,?]
> y
test_alias()
# end migration needs to be applied before running this query
select test_alias();
如果上述query可以成功執行,就代表我們定義的alias
都沒有問題。
insert
此場景的Scene
因為剛剛已經透過test_alias()
測試了所有alias
,所以這裡我們可以放心使用。
值得注意的是,我們在這裡使用nested insert
,同時insert
了Scene object
及佛堂
這個Location object
:
insert Scene {
title:= "韓琛初現",
detail:= "韓琛準備派遣多個身家較為清白的小弟臥底至香港警隊,包括建明。" ++
"他向小弟們講述著自己的過去,並說自己不相信算命先生所說的" ++
"「一將功成萬骨枯」。他認為出來混的,未來的路怎麼走應該由自己決定。",
remarks:= "1.假設此場景為1992年。",
who:= {hon, lau},
`when`:= year_1992,
where:= (insert Location {name:= "佛堂"}) ,
references:= [
(
"維基百科-無間道",
"https://zh.wikipedia.org/zh-tw/%E7%84%A1%E9%96%93%E9%81%93"
),
(
"香港警察職級",
"https://zh.wikipedia.org/zh-tw/%E9%A6%99%E6%B8%AF%E8%AD%A6%E5%AF%9F%E8%81%B7%E7%B4%9A"
)
]
};
理論上,我們應該處理建明由Gangster object
轉變到GangsterSpy object
的過程,但這對於首幕
來說,可能太過複雜,所以在此處直接insert
建明為GangsterSpy object
。同理,我們將於次幕
直接insert
永仁為PoliceSpy object
,而不處理其由Police object
轉變到PoliceSpy object
的過程。
佛堂前六個骨灰罈暗指當年韓琛死於屯門的六個兄弟。他藉祭拜為由,於一眾小弟面前展現其「仁義之風」。